// Copyright (c) 2021 Terminus, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
	"fmt"
	"go/format"
	"io/fs"
	"os"
	"path/filepath"
	"sort"
	"strings"
	"text/template"
	"time"
)

const basePackagePath = "github.com/erda-project/erda/internal/apps/ai-proxy/route/filters"

// FilterImport represents a filter import
type FilterImport struct {
	Name        string // filter name, e.g. "context"
	ImportPath  string // complete import path
	Type        string // "request" or "response"
	PackageName string // package name
}

// TemplateData template data
type TemplateData struct {
	GeneratedTime   string
	RequestFilters  []FilterImport
	ResponseFilters []FilterImport
	BasePackagePath string
}

const allGoTemplate = `// Copyright (c) 2021 Terminus, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Code generated by go generate; DO NOT EDIT.
// This file was generated at {{ .GeneratedTime }}

//go:generate go run generate.go

package all

// This package imports all filters for automatic registration
// Request filters ({{ len .RequestFilters }} filters)
import (
{{- range .RequestFilters }}
	_ "{{ .ImportPath }}"
{{- end }}
)

// Response filters ({{ len .ResponseFilters }} filters)
import (
{{- range .ResponseFilters }}
	_ "{{ .ImportPath }}"
{{- end }}
)

// Auto-generated filter imports:
// Request filters:
{{- range .RequestFilters }}
//   - {{ .Name }} ({{ .ImportPath }})
{{- end }}
// Response filters:
{{- range .ResponseFilters }}
//   - {{ .Name }} ({{ .ImportPath }})
{{- end }}
`

func main() {
	if err := generateAllGo(); err != nil {
		fmt.Fprintf(os.Stderr, "Error: %v\n", err)
		os.Exit(1)
	}
}

func generateAllGo() error {
	// Get current directory
	currentDir, err := os.Getwd()
	if err != nil {
		return fmt.Errorf("failed to get current directory: %w", err)
	}

	// Get path to filters directory
	filtersDir := filepath.Join(currentDir, "..", "..")

	// Scan request and response filters
	requestFilters, err := scanFilters(filepath.Join(filtersDir, "request"), "request")
	if err != nil {
		return fmt.Errorf("failed to scan request filters: %w", err)
	}

	responseFilters, err := scanFilters(filepath.Join(filtersDir, "response"), "response")
	if err != nil {
		return fmt.Errorf("failed to scan response filters: %w", err)
	}

	// Sort to ensure stable generated code
	sort.Slice(requestFilters, func(i, j int) bool {
		return requestFilters[i].Name < requestFilters[j].Name
	})
	sort.Slice(responseFilters, func(i, j int) bool {
		return responseFilters[i].Name < responseFilters[j].Name
	})

	// Prepare template data
	data := TemplateData{
		GeneratedTime:   time.Now().Format("2006-01-02 15:04:05"),
		RequestFilters:  requestFilters,
		ResponseFilters: responseFilters,
		BasePackagePath: basePackagePath,
	}

	// Parse template
	tmpl, err := template.New("all.go").Parse(allGoTemplate)
	if err != nil {
		return fmt.Errorf("failed to parse template: %w", err)
	}

	// Generate code
	var buf strings.Builder
	if err := tmpl.Execute(&buf, data); err != nil {
		return fmt.Errorf("failed to execute template: %w", err)
	}

	// Format code
	formatted, err := format.Source([]byte(buf.String()))
	if err != nil {
		return fmt.Errorf("failed to format generated code: %w", err)
	}

	// Write to file
	outputFile := "../all.go"
	if err := os.WriteFile(outputFile, formatted, 0644); err != nil {
		return fmt.Errorf("failed to write file %s: %w", outputFile, err)
	}

	fmt.Printf("Generated %s with %d request filters and %d response filters\n",
		outputFile, len(requestFilters), len(responseFilters))

	return nil
}

func scanFilters(dir, filterType string) ([]FilterImport, error) {
	var filters []FilterImport

	// Check if directory exists
	if _, err := os.Stat(dir); os.IsNotExist(err) {
		fmt.Printf("Directory %s does not exist, skipping\n", dir)
		return filters, nil
	}

	err := filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
		if err != nil {
			return err
		}

		// Only process directories
		if !d.IsDir() {
			return nil
		}

		// Skip root directory
		if path == dir {
			return nil
		}

		// Get relative path
		relPath, err := filepath.Rel(dir, path)
		if err != nil {
			return err
		}

		// Check if contains Go files
		hasGoFiles, err := hasGoFiles(path)
		if err != nil {
			return err
		}

		if !hasGoFiles {
			return nil // Continue traversing subdirectories, don't skip
		}

		// Build import path
		filterName := relPath
		importPath := fmt.Sprintf("%s/%s/%s", basePackagePath, filterType, filterName)
		packageName := strings.ReplaceAll(filterName, "-", "_")

		filters = append(filters, FilterImport{
			Name:        filterName,
			ImportPath:  importPath,
			Type:        filterType,
			PackageName: packageName,
		})

		return nil // Continue recursing into subdirectories
	})

	if err != nil {
		return nil, err
	}

	return filters, nil
}

func hasGoFiles(dir string) (bool, error) {
	entries, err := os.ReadDir(dir)
	if err != nil {
		return false, err
	}

	for _, entry := range entries {
		if !entry.IsDir() && strings.HasSuffix(entry.Name(), ".go") && !strings.HasSuffix(entry.Name(), "_test.go") {
			return true, nil
		}
	}

	return false, nil
}
